/*
 *  Cache.cpp
 *  gre
 *
 *  Created by Kutlak Roman on 20/10/2011.
 *  Copyright 2011 Kutlak Roman. All rights reserved.
 *
 */


#ifndef gre_Cache_cpp
#define gre_Cache_cpp

#include <fstream>
#include <utility>
#include <iostream>
#include <string>
using std::ifstream;
using std::ofstream;
using std::istream;
using std::ostream;
using std::cerr;
using std::endl;
using std::string;
using std::map;
using std::pair;

namespace kutlak {
namespace roman {
namespace gre {
    
    
    // class Cache
    /////////////////////////////// PUBLIC   ///////////////////////////////////
    
#pragma mark -
#pragma mark lifecycle
    template <class K, class V>
    Cache<K,V>::Cache(size_t maxSize, const V& defaultValue) :
    fModified(false), mMaxSize(maxSize),
    mDefaultValue(defaultValue) {} // Cache
    
    template<class K, class V>
    Cache<K,V>::Cache(const Cache<K,V>& from) :
    fModified(from.fModified), mMaxSize(from.mMaxSize),
    mDefaultValue(from.mDefaultValue),
    mCache(from.mCache), mOrder(from.mOrder)  {} // Cache
    
    template<class K, class V>
    Cache<K,V>::Cache(Cache<K,V>&& from) :
    fModified(false), mMaxSize(from.mMaxSize),
    mDefaultValue(std::move(from.mDefaultValue)),
    mCache(std::move(from.mCache)),
    mOrder(std::move(from.mOrder)) {}
    
    template<class K, class V>
    Cache<K,V>& Cache<K,V>::operator=(Cache from) 
    {
        swap(*this, from);
        return *this;
    } // =
    
    template <class K, class V>
    Cache<K, V >::~Cache() noexcept(true) = default;
    
    template <class K, class V>
    void swap(Cache<K,V>& left, Cache<K,V>& right) 
    {
        using std::swap;
        
        swap(left.fModified, right.fModified);
        swap(left.mMaxSize, right.mMaxSize);
        swap(left.mDefaultValue, right.mDefaultValue);
        swap(left.mCache, right.mCache);
        swap(left.mOrder, right.mOrder);
    }
    
#pragma mark -
#pragma mark operators
    template <class K, class V>
    Cache<K,V>& Cache<K,V>::operator+=(const Cache<K,V>& rhs) 
    {
        // TODO: use std::copy and add std::move 
        typename map<K,V>::const_iterator it;
        typename map<K,V>::const_iterator end;
        for (auto& it = rhs.mCache.begin(); it != rhs.mCache.end(); ++it) 
        {
            this->Insert(it->first, it->second);
        }
        return *this;
    }
    
    template <class K, class V>
    bool Cache<K,V>::operator==(const Cache<K,V>& rhs) const 
    {
        if (fModified == rhs.fModified &&
            mMaxSize  == rhs.mMaxSize) 
        {
            V dummy;
            for (const auto& p : mCache) 
            {
                if (!rhs.Find(p.first, dummy) || p.second != dummy)
                    return false;
            }
        }
        return true;
    }
    
    template <class K, class V>
    bool Cache<K,V>::operator!=(const Cache<K,V>& rhs) const 
    {
        return !(*this == rhs);
    }

#pragma mark -
#pragma mark I/O
    
    template <class K, class V>
    ostream& operator<<(ostream& out, Cache<K,V>& obj) 
    {
        obj.Serialise(out);
        return out;
    }
    
    template <class K, class V>
    istream& operator>>(istream& input, Cache<K,V>& obj) 
    {
        Cache<K,V> tmp;
        tmp.Deserialise(input);
        swap(tmp, obj);
        return input;
    }
    
    
#pragma mark -
#pragma mark operations
    //=============================  OPERATIONS  ===============================
    
//    template <class K, class V>
//    V Cache<K,V>::Find(const K& key) const 
//    {
//        typename map<K, V>::const_iterator it = mCache.find(key);
//        if (mCache.end() != it)
//            return it->second;
//        return mDefaultValue;
//    }
//    
//    template <class K, class V>
//    bool Cache<K,V>::Find(const K& key, V& result) const 
//    {
//        typename map<K, V>::const_iterator it = mCache.find(key);
//        if (mCache.end() != it) 
//        {
//            result = it->second;
//            return true;
//        }
//        // do not change the value as it might erase the old val.
//        return false;
//    }
//    
//    template <class K, class V>
//    bool Cache<K,V>::Insert(const K& key, const V& value) 
//    {
//        // TODO: respect mMaxSize
//        mCache[key] = value;
//        fModified = true;
//        return true;
//    }
//    
//    template <class K, class V>
//    bool Cache<K,V>::Insert(K&& key, V&& value) 
//    {
//        // TODO: respect mMaxSize
//        mCache.emplace(std::move(key), std::move(value));
//        fModified = true;
//        return true;
//    }
    
    template <class K, class V>
    V Cache<K,V>::Find(const K& key) const
    {
        V result = mDefaultValue;

        this->Find(key, result);
        
        return result;
    }
    
    
    template <class K, class V>
    bool Cache<K,V>::Find(const K& key, V& result) const
    {
        auto it = mCache.find(key);
        if (mCache.end() != it) 
        {
//            // promote the current result so it is on top of the search vector
//            if (mCache.begin() != it)
//            {
//                prev = it;
//                --prev;
//                std::swap(*prev, *it);
//                result = prev->second;
//                return true;
//            }
            result = it->second;
            return true;
        }
        // do not change the value as it might erase the old val.
        return false;
    }
    
    template <class K, class V>
    bool Cache<K,V>::Insert(const K& key, const V& value) 
    {
        return this->Insert(K(key), V(value));
    }
    
    template <class K, class V>
    bool Cache<K,V>::Insert(K&& key, V&& value) 
    {
        mOrder.push_front(key);
        auto res = mCache.emplace(std::move(key), std::move(value));
        if (mOrder.size() > mMaxSize)
        {
            K last = mOrder.back();
            mOrder.pop_back();
            mCache.erase(last);
        }
        
        fModified = true;
        return res.second;
    }
    
    template <class K, class V>
    void Cache<K,V>::Clear() 
    {
        mCache.erase(mCache.begin(), mCache.end());
        fModified = true;
    }

    
#pragma mark -
#pragma mark inquiry
    //=============================  INQUIRY     ===============================
    template <class K, class V>
    bool Cache<K,V>::IsModified() const 
    {
        return fModified;
    }
    
#pragma mark -
#pragma mark I/O
    
    template <class K, class V>
    void Cache<K,V>::Serialise(ostream& out) const 
    {
        out << "<cache>\n";
        out << mDefaultValue << endl;
        out << mMaxSize << endl;
        
        typename map<K, V>::const_iterator cacheEntry, e;
        for (const auto& cacheEntry : mCache) 
        {
            out << cacheEntry.first << "\n";
            out << cacheEntry.second << "\n";
        }
        out << "</cache>\n";
    }
    
    template <class K, class V>
    void Cache<K,V>::Deserialise(istream& input) 
    {
        string tmp;

        if (!(input >> tmp) || "<cache>" != tmp) 
        {
            string message = "Expected <cache>.";
            throw DeserialisationException(std::move(message),
                                           std::move(tmp));
        }
        
        if (!(input >> mDefaultValue >> std::ws))
        {
            string message = "Expected default value.";
            throw DeserialisationException(std::move(message));
        }
        
        if (!(input >> mMaxSize >> std::ws))
        {
            string message = "Expected max size.";
            throw DeserialisationException(std::move(message));
        }
        
        try 
        {
            K key;
            V value;
            
            while (input >> key >> std::ws >> value >> std::ws)
                this->Insert(std::move(key), std::move(value));
        } 
        catch (DeserialisationException& de) 
        {
            if ("</cache>" != de.data()) 
            {
                string message = "Exception while deserialising Cache: "; 
                throw DeserialisationException(message + de.what(), 
                                               std::move(de.data()));
            }
        }
    }
    
    // class FileCache
    /////////////////////////////// PUBLIC   ///////////////////////////////////
    
#pragma mark -
#pragma mark lifecycle
    template <class K, class V>
    FileCache<K,V>::FileCache(const std::string& file, 
                              size_t maxSize,
                              const V& defaultValue) :
    Cache<K,V>(maxSize, defaultValue), mFileName(file) 
    {
        if (!file.empty() && !ReadFile(file))
            throw DeserialisationException("Exception while constructing FileCache.");
    } // Cache
    
    template<class K, class V>
    FileCache<K,V>::FileCache(const FileCache<K,V>& from) :
    Cache<K,V>(from), mFileName(from.mFileName) {} // Cache
    
    template<class K, class V>
    FileCache<K,V>::FileCache(FileCache<K,V>&& from) :
    Cache<K,V>(std::move(from)), mFileName(from.File()) {}
    
    template<class K, class V>
    FileCache<K,V>& FileCache<K,V>::operator=(FileCache from) 
    {    
        swap(*this, from);
        return *this;
    } // =
    
    template <class K, class V>
    FileCache<K, V >::~FileCache() noexcept(true) = default;
    
    template <class K, class V>
    void swap(FileCache<K,V>& left, FileCache<K,V>& right) 
    {
        using std::swap;
        
        swap(dynamic_cast<Cache<K,V>&>(left), dynamic_cast<Cache<K, V>&>(right));
        swap(left.mFileName, right.mFileName);
    }
    
    // operators
    
    template <class K, class V>
    bool FileCache<K,V>::operator==(const FileCache<K,V>& rhs) const 
    {
        return (mFileName == rhs.mFileName &&
                dynamic_cast<const Cache<K,V>&>(*this) == 
                dynamic_cast<const Cache<K,V>&>(rhs) );
    }
    
    template <class K, class V>
    bool FileCache<K,V>::operator!=(const FileCache<K,V>& rhs) const 
    {
        return !(*this == rhs);
    }
    
#pragma mark -
#pragma mark FileCache access
    //=============================  ACESS       ===============================
    template <class K, class V>
    const std::string& FileCache<K,V>::File() const 
    {
        return mFileName;
    }
    
    template <class K, class V>
    bool FileCache<K,V>::SetFile(const std::string& file, bool replace) 
    {
        if (replace) 
        {
            bool result = true;
            if (file != mFileName && this->fModified) 
            {
                try 
                {
                    FileCache<K,V> tmp(file, this->mMaxSize, this->mDefaultValue);
                    Save();
                    swap(*this, tmp);
                    mFileName = file;
                } 
                catch (DeserialisationException& de) 
                {
                    cerr << de.what() << endl;
                    cerr << "\tRead: " << de.data() << endl;
                    result = false;
                }
            }
            return result;
        } 
        else 
        {
            mFileName = file;
        }
        return true;
    }
    
    template <class K, class V>
    const V& Cache<K,V>::DefaultValue() const 
    {
        return mDefaultValue;
    }
    
    template <class K, class V>
    size_t Cache<K,V>::MaxSize() const
    {
        return mMaxSize;
    }
    
    template <class K, class V>
    size_t Cache<K,V>::Size() const
    {
        return mCache.size();
    }
    
    template <class K, class V>
    bool FileCache<K,V>::Save() 
    {
        bool result = false;
        if (this->fModified) 
        {
            result = WriteFile(mFileName);
            if (result)
                this->fModified = false;
        }
        return result;
    }
    
#pragma mark -
#pragma mark FileCache I/O
    
    template <class K, class V>
    bool FileCache<K,V>::WriteFile(const string& file) const 
    {
        ofstream output;
        output.open(file.c_str());
        
        if (!output.is_open()) 
        {
            cerr << __FILE__ << " (" << __LINE__ << "): ";
            cerr << "Could not open the output file ";
            cerr << file << endl;
            return false;
        }
        
        this->Serialise(output);
        output.close();
        return true;
    }
    
    
    template <class K, class V>
    bool FileCache<K,V>::ReadFile(const string& file) 
    {
        ifstream input;
        input.open(file.c_str());
        if (!input.is_open()) 
        {
            cerr << __FILE__ << " (" << __LINE__ << "): ";
            cerr << "Could not open the input file ";
            cerr << file << endl;
            return false;
        }
        
        this->Deserialise(input);
        input.close();
        this->fModified = false;
        return true;
    }
    
} // end namespace gre
}
}

#endif
